/*
 *  $Id: Widget.cs 1232 2010-12-07 13:16:37Z thenn.erannor $
 *  This file is a part of Ragima CCG.
 *  (C) 2008-2010 Ragima development group.
 *
 *  Licensed under the terms of the GNU GPL License version 2.
 *
 */

using System;

using AgateLib.DisplayLib;
using AgateLib.Geometry;
using AgateLib.InputLib;

using Nereal.Extensions;
using Nereal.Serialization;

using Ragima.Helper;

namespace Ragima.UI {
    /// <summary>
    /// Абстрактный виджет: элемент пользовательского интерфейса.
    /// </summary>
    [TypeId("widget"), XmlRootName("widget"), TypeResolver(typeof(AttrTypeResolver<Widget>), null)]
    public class Widget : IKeyed<string> {
        private const bool DefaultVisible = true;

        private Widget _parent;
        private string _id;
        private Rectangle _rect;
        private bool _visible;
        private Cursor _cursor;
        private Resource<Font> _font;
        private Nullable<Color> _color, _backColor;
        private bool _hovered, _focused;

        public Widget() {
            _parent = null;
            _id = string.Empty;
            _rect = DefaultBounds;
            _visible = DefaultVisible;
            _cursor = null;
            _font = null;
            _color = null;
            _backColor = null;
        }

        /// <summary>
        /// Идентификатор объекта для поиска внутри сцены.
        /// </summary>
        [XmlName("id"), DefaultValue("")]
        public string Id {
            get { return _id; }
            set { _id = value; }
        }

        #region IKeyed[System.String] implementation
        string IKeyed<string>.Key {
            get { return _id; }
        }
        #endregion

        #region Положение в дереве виджетов и отношения с другими виджетами.
        /// <summary>
        /// Родительский виджет.
        /// </summary>
        [NotSerialize]
        public Widget Parent {
            get { return _parent; }
            set {
                if (_parent == value)
                    return;
                if (_parent != null)
                    _parent.RemoveChild(this);
                _parent = value;
                if (_parent != null)
                    _parent.AddChild(this);
            }
        }

        /// <summary>
        /// Сцена, в которой находится данный виджет.
        /// </summary>
        [NotSerialize]
        public Scene ParentScene {
            get { return GetParent<Scene>(); }
        }

        /// <summary>
        /// Получает родительский виджет указанного типа.
        /// </summary>
        public T GetParent<T>() where T : Widget {
            var parent = Parent;
            while (parent != null && !(parent is T))
                parent = parent.Parent;
            return (T) parent;
        }

        /// <summary>
        /// Добавляет указанный виджет в текущий.
        /// </summary>
        protected virtual void AddChild(Widget widget) {
        }
        /// <summary>
        /// Добавляет указанный виджет из текущего.
        /// </summary>
        protected virtual void RemoveChild(Widget widget) {
        }

        /// <summary>
        /// Ищет виджет по идентификатору. Область поиска: данный виждет и все вложенные.
        /// </summary>
        public virtual Widget FindWidget(string id) {
            return id == _id ? this : null;
        }

        /// <summary>
        /// Ищет виджет по идентификатору. Область поиска: сцена, в которую входит данный виждет.
        /// </summary>
        public Widget FindSceneWidget(string id) {
            return (ParentScene ?? this).FindWidget(id);
        }
        #endregion

        #region Расположение и размеры
        /// <summary>
        /// Расположение и размеры.
        /// </summary>
        // TODO : maybe member default value
        [XmlName("bounds")]
        public Rectangle Bounds {
            get { return _rect; }
            set { _rect = value; }
        }

        /// <summary>
        /// Расположение и размеры по умолчанию.
        /// </summary>
        public virtual Rectangle DefaultBounds {
            get { return Rectangle.Empty; }
        }

        /// <summary>
        /// Расположение.
        /// </summary>
        [NotSerialize]
        public Point Location {
            get { return _rect.Location; }
            set { _rect.Location = value; }
        }

        /// <summary>
        /// Нижний правый угол.
        /// </summary>
        [NotSerialize]
        public Point BottomRight {
            get { return new Point(_rect.Right, _rect.Bottom); }
            set {
                Right = value.X;
                Bottom = value.Y;
            }
        }

        /// <summary>
        /// Размеры
        /// </summary>
        [NotSerialize]
        public Size Size {
            get { return _rect.Size; }
            set { _rect.Size = value; }
        }

        /// <summary>
        /// Левая граница.
        /// </summary>
        [NotSerialize]
        public int Left {
            get { return _rect.Left; }
            set { _rect.X = value; }
        }

        /// <summary>
        /// Верхняя граница.
        /// </summary>
        [NotSerialize]
        public int Top {
            get { return _rect.Top; }
            set { _rect.Y = value; }
        }

        /// <summary>
        /// Правая граница.
        /// </summary>
        [NotSerialize]
        public int Right {
            get { return _rect.Right; }
            set { _rect.Width = value - _rect.Left; }
        }

        /// <summary>
        /// Нижняя граница.
        /// </summary>
        [NotSerialize]
        public int Bottom {
            get { return _rect.Bottom; }
            set { _rect.Height = value - _rect.Top; }
        }

        /// <summary>
        /// Ширина.
        /// </summary>
        [NotSerialize]
        public int Width {
            get { return _rect.Width; }
            set { _rect.Width = value; }
        }

        /// <summary>
        /// Высота.
        /// </summary>
        [NotSerialize]
        public int Height {
            get { return _rect.Height; }
            set { _rect.Height = value; }
        }

        /// <summary>
        /// Абсолютные расположение и размеры.
        /// </summary>
        public Rectangle AbsoluteBounds {
            get { return new Rectangle(AbsoluteLocation, _rect.Size); }
        }

        /// <summary>
        /// Абсолютное расположение.
        /// </summary>
        public Point AbsoluteLocation {
            get {
                if (Parent == null)
                    return _rect.Location;
                Point location = Parent.AbsoluteLocation;
                location.X += _rect.X;
                location.Y += _rect.Y;
                return location;
            }
        }

        /// <summary>
        /// Положение курсора мыши относительно виджета.
        /// </summary>
        public Point MouseLocation {
            get { return Mouse.Position.Sub(AbsoluteLocation); }
        }
        #endregion

        #region Видимость, курсор, шрифт, цвета, и прочее.
        /// <summary>
        /// Видимость виджета.
        /// </summary>
        [XmlName("visible"), DefaultValue(DefaultVisible)]
        public bool Visible {
            get { return _visible; }
            set { _visible = value; }
        }

        /// <summary>
        /// Находится ли курсор мыши над виджетом.
        /// </summary>
        public bool Hovered {
            get { return _hovered; }
        }

        /// <summary>
        /// Находится ли фокус на виджете.
        /// </summary>
        public bool Focused {
            get { return _focused; }
        }

        /// <summary>
        /// Ресурс курсора мыши.
        /// </summary>
        [XmlName("cursor"), Reference(true, typeof(string), "cursor_id")]
        public Cursor Cursor {
            get { return _cursor; }
            set { _cursor = value; }
        }

        /// <summary>
        /// Курсор мыши для виджета.
        /// </summary>
        public virtual Cursor VisibleCursor {
            get { return _cursor ?? (_parent != null ? _parent.VisibleCursor : null); }
        }

        /// <summary>
        /// Шрифт для виджета.
        /// </summary>
        [NotSerialize]
        public virtual Font Font {
            get { return Resource.Exists(_font) ? _font.Data : (_parent != null ? _parent.Font : null); }
        }

        /// <summary>
        /// Ресурс шрифта.
        /// </summary>
        [XmlName("font")]
        public Resource<Font> FontResource {
            get { return _font; }
            set { _font = value; }
        }

        /// <summary>
        /// Основной цвет: текста или чего-то другого рисуемого.
        /// </summary>
        [XmlName("color")]
        public Nullable<Color> MainColor {
            get { return _color; }
            set { _color = value; }
        }

        /// <summary>
        /// Фоновый цвет.
        /// </summary>
        [XmlName("backcolor")]
        public Nullable<Color> BackColor {
            get { return _backColor; }
            set { _backColor = value; }
        }

        /// <summary>
        /// Фоновый цвет по умолчанию.
        /// </summary>
        protected virtual Nullable<Color> DefaultBackColor {
            get { return null; }
        }
        #endregion

        #region Отрисовка и обработка
        /// <summary>
        /// Рисует виджет.
        /// </summary>
        public virtual void Draw() {
            DrawBackground();
        }

        /// <summary>
        /// Рисует фон виджета.
        /// </summary>
        protected virtual void DrawBackground() {
            var color = _backColor ?? DefaultBackColor;
            if (color.HasValue)
                Display.FillRect(AbsoluteBounds, color.Value);
        }

        /// <summary>
        /// Обрабатывает виджет.
        /// </summary>
        public virtual void Process() {
        }

        /// <summary>
        /// Ищет вложенный виджет по указанной точке.
        /// </summary>
        public virtual Widget FindChild(Point point) {
            return _visible && _rect.Contains(point) ? this : null;
        }

        /// <summary>
        /// Устаналивает фокус на данный виджет.
        /// </summary>
        public void SetFocus() {
            if (_focused)
                return;
            var scene = ParentScene;
            if (scene != null)
                scene.SetFocus(this);
        }

        /// <summary>
        /// Снимает фокус с данного виджета.
        /// </summary>
        public void ResetFocus() {
            if (!_focused)
                return;
            var scene = ParentScene;
            if (scene != null)
                scene.SetFocus(null);
        }
        #endregion

        #region События
        /// <summary>
        /// Событие появления курсора мыши на виджете.
        /// </summary>
        public virtual void DoMouseEnter() {
            _hovered = true;
        }

        /// <summary>
        /// Событие исчезновения курсора мыши с виджета.
        /// </summary>
        public virtual void DoMouseLeave() {
            _hovered = false;
        }

        /// <summary>
        /// Событие перемещения курсора мыши на виджете.
        /// </summary>
        /// <param name="point">Текущее положение курсора внутри виджета.</param>
        public virtual void DoMouseMove(Point point) {
        }

        /// <summary>
        /// Событие нажатия/отпускания кнопки мыши на виджета.
        /// </summary>
        /// <param name="buttons">Нажатая кнопка.</param>
        /// <param name="down">Кнопка нажата или отпущена.</param>
        public virtual void DoClick(Mouse.MouseButtons buttons, bool down) {
            if (Parent != null)
                Parent.DoClick(buttons, down);
        }

        /// <summary>
        /// Событие нажатия клавиши.
        /// </summary>
        /// <param name="ev">Параметры нажатой клавиши.</param>
        /// <returns>True, если нажатие клавиши обработано виджетом.</returns>
        public virtual bool DoKeyPress(KeyboardEvent ev) {
            return false;
        }

        /// <summary>
        /// Событие установки фокуса на виджет.
        /// </summary>
        public virtual void DoFocus() {
            _focused = true;
        }

        /// <summary>
        /// Событие потери фокуса виджетом.
        /// </summary>
        public virtual void DoLostFocus() {
            _focused = false;
        }
        #endregion
    }
}
